const jwt = require('jsonwebtoken')
const bcrypt = require('bcryptjs')
const crypto = require('crypto')
const User = require('../models/User')
const config = require('../config')
const logger = require('../utils/logger')
const { generateUUID } = require('../utils/crypto')
const { ERROR_CODES, USER_ROLES, TOKEN_TYPES } = require('../utils/constants')
const { AuthenticationError, ValidationError, NotFoundError } = require('../middleware/errorHandler')

class AuthService {
  /**
   * 用户注册
   */
  static async register(userData) {
    try {
      const { username, email, password, role = USER_ROLES.STUDENT } = userData
      
      // 检查用户名是否已存在
      const existingUser = await User.findOne({ where: { username } })
      if (existingUser) {
        throw new ValidationError('用户名已存在')
      }
      
      // 检查邮箱是否已存在
      const existingEmail = await User.findOne({ where: { email } })
      if (existingEmail) {
        throw new ValidationError('邮箱已存在')
      }
      
      // 生成用户ID
      const userId = generateUUID()
      
      // 加密密码
      const hashedPassword = await bcrypt.hash(password, 12)
      
      // 创建用户
      const user = await User.create({
        id: userId,
        username,
        email,
        password: hashedPassword,
        role,
        isActive: true
      })
      
      // 生成令牌
      const tokens = this.generateTokens(user)
      
      // 更新最后登录时间
      await user.update({ lastLoginAt: new Date() })
      
      logger.info('User registered successfully', {
        userId: user.id,
        username: user.username,
        email: user.email,
        role: user.role
      })
      
      // 返回用户信息（不包含密码）
      const { password: _, ...userWithoutPassword } = user.toJSON()
      
      return {
        user: userWithoutPassword,
        tokens
      }
    } catch (error) {
      logger.error('Registration failed', {
        error: error.message,
        userData: { username: userData.username, email: userData.email }
      })
      throw error
    }
  }
  
  /**
   * 用户登录
   */
  static async login(credentials) {
    try {
      const { username, password } = credentials
      
      // 查找用户（支持用户名或邮箱登录）
      const user = await User.findOne({
        where: {
          [require('sequelize').Op.or]: [
            { username },
            { email: username }
          ]
        }
      })
      
      if (!user) {
        throw new AuthenticationError(ERROR_CODES.INVALID_CREDENTIALS, '用户名或密码错误')
      }
      
      // 检查用户状态
      if (!user.isActive) {
        throw new AuthenticationError(ERROR_CODES.USER_DISABLED, '账户已被禁用')
      }
      
      // 验证密码
      const isValidPassword = await bcrypt.compare(password, user.password)
      if (!isValidPassword) {
        throw new AuthenticationError(ERROR_CODES.INVALID_CREDENTIALS, '用户名或密码错误')
      }
      
      // 生成令牌
      const tokens = this.generateTokens(user)
      
      // 更新最后登录时间
      await user.update({ lastLoginAt: new Date() })
      
      logger.info('User logged in successfully', {
        userId: user.id,
        username: user.username,
        role: user.role
      })
      
      // 返回用户信息（不包含密码）
      const { password: _, ...userWithoutPassword } = user.toJSON()
      
      return {
        user: userWithoutPassword,
        tokens
      }
    } catch (error) {
      logger.error('Login failed', {
        error: error.message,
        username: credentials.username
      })
      throw error
    }
  }
  
  /**
   * 刷新访问令牌
   */
  static async refreshToken(refreshToken) {
    try {
      if (!refreshToken) {
        throw new AuthenticationError(ERROR_CODES.REFRESH_TOKEN_MISSING, '刷新令牌缺失')
      }
      
      // 验证刷新令牌
      const decoded = jwt.verify(refreshToken, config.jwt.refreshSecret)
      
      if (decoded.type !== TOKEN_TYPES.REFRESH) {
        throw new AuthenticationError(ERROR_CODES.INVALID_REFRESH_TOKEN, '无效的刷新令牌')
      }
      
      // 获取用户信息
      const user = await User.findByPk(decoded.userId)
      if (!user) {
        throw new NotFoundError('用户', decoded.userId)
      }
      
      // 检查用户状态
      if (user.status !== 'active') {
        throw new AuthenticationError(ERROR_CODES.USER_DISABLED, '账户已被禁用')
      }
      
      // 生成新的令牌
      const tokens = this.generateTokens(user)
      
      logger.info('Token refreshed successfully', {
        userId: user.id,
        username: user.username
      })
      
      return {
        user: User.sanitizeUser(user),
        tokens
      }
    } catch (error) {
      if (error.name === 'JsonWebTokenError' || error.name === 'TokenExpiredError') {
        throw new AuthenticationError(ERROR_CODES.INVALID_REFRESH_TOKEN, '无效的刷新令牌')
      }
      
      logger.error('Token refresh failed', {
        error: error.message
      })
      throw error
    }
  }
  
  /**
   * 用户登出
   */
  static async logout(userId, token) {
    try {
      // 这里可以实现令牌黑名单功能
      // 由于使用JWT，通常通过设置较短的过期时间来处理登出
      
      logger.info('User logged out', {
        userId
      })
      
      return { success: true }
    } catch (error) {
      logger.error('Logout failed', {
        error: error.message,
        userId
      })
      throw error
    }
  }
  
  /**
   * 修改密码
   */
  static async changePassword(userId, passwordData) {
    try {
      const { currentPassword, newPassword } = passwordData
      
      // 获取用户信息
      const user = await User.findByPk(userId)
      if (!user) {
        throw new NotFoundError('用户', userId)
      }
      
      // 验证当前密码
      const isValidPassword = await bcrypt.compare(currentPassword, user.password)
      if (!isValidPassword) {
        throw new AuthenticationError(ERROR_CODES.INVALID_CREDENTIALS, '当前密码错误')
      }
      
      // 加密新密码并更新
      const hashedNewPassword = await bcrypt.hash(newPassword, 12)
      await user.update({ password: hashedNewPassword })
      
      logger.info('Password changed successfully', {
        userId,
        username: user.username
      })
      
      return { success: true }
    } catch (error) {
      logger.error('Password change failed', {
        error: error.message,
        userId
      })
      throw error
    }
  }
  
  /**
   * 重置密码请求
   */
  static async requestPasswordReset(email) {
    try {
      // 查找用户
      const user = await User.findOne({ where: { email } })
      if (!user) {
        // 为了安全，不透露用户是否存在
        logger.warn('Password reset requested for non-existent email', { email })
        return { success: true }
      }
      
      // 生成重置令牌
      const resetToken = crypto.randomBytes(32).toString('hex')
      const resetTokenExpiry = new Date(Date.now() + 3600000) // 1小时后过期
      
      // 保存重置令牌（这里简化处理，实际应用中可能需要单独的表）
      // 注意：当前User模型没有resetToken字段，这里先注释掉
      // await user.update({
      //   resetToken,
      //   resetTokenExpiry
      // })
      
      // 这里应该发送重置邮件
      // await emailService.sendPasswordResetEmail(user.email, resetToken)
      
      logger.info('Password reset requested', {
        userId: user.id,
        email: user.email
      })
      
      return { success: true }
    } catch (error) {
      logger.error('Password reset request failed', {
        error: error.message,
        email
      })
      throw error
    }
  }
  
  /**
   * 重置密码
   */
  static async resetPassword(token, newPassword) {
    try {
      // 查找具有有效重置令牌的用户
      const users = await User.findAll()
      const user = users.find(u => 
        u.resetToken === token && 
        u.resetTokenExpiry && 
        new Date(u.resetTokenExpiry) > new Date()
      )
      
      if (!user) {
        throw new AuthenticationError(ERROR_CODES.INVALID_CREDENTIALS, '无效或已过期的重置令牌')
      }
      
      // 更新密码并清除重置令牌
      const hashedPassword = await bcrypt.hash(newPassword, 12)
      await user.update({
        password: hashedPassword,
        resetToken: null,
        resetTokenExpiry: null
      })
      
      logger.info('Password reset successfully', {
        userId: user.id,
        username: user.username
      })
      
      return { success: true }
    } catch (error) {
      logger.error('Password reset failed', {
        error: error.message
      })
      throw error
    }
  }
  
  /**
   * 验证访问令牌
   */
  static async verifyAccessToken(token) {
    try {
      const decoded = jwt.verify(token, config.jwt.secret)
      
      if (decoded.type !== TOKEN_TYPES.ACCESS) {
        throw new AuthenticationError(ERROR_CODES.TOKEN_INVALID, '无效的访问令牌')
      }
      
      // 获取用户信息
      const user = await User.findByPk(decoded.userId)
      if (!user) {
        throw new NotFoundError('用户', decoded.userId)
      }
      
      // 检查用户状态
      if (user.status !== 'active') {
        throw new AuthenticationError(ERROR_CODES.USER_DISABLED, '账户已被禁用')
      }
      
      return {
        user: User.sanitizeUser(user),
        decoded
      }
    } catch (error) {
      if (error.name === 'JsonWebTokenError') {
        throw new AuthenticationError(ERROR_CODES.TOKEN_INVALID, '无效的访问令牌')
      }
      if (error.name === 'TokenExpiredError') {
        throw new AuthenticationError(ERROR_CODES.TOKEN_EXPIRED, '访问令牌已过期')
      }
      throw error
    }
  }
  
  /**
   * 生成访问令牌和刷新令牌
   */
  static generateTokens(user) {
    const payload = {
      userId: user.id,
      username: user.username,
      role: user.role
    }
    
    // 生成访问令牌
    const accessToken = jwt.sign(
      {
        ...payload,
        type: TOKEN_TYPES.ACCESS
      },
      config.jwt.secret,
      {
        expiresIn: config.jwt.expiresIn,
        issuer: config.jwt.issuer,
        audience: config.jwt.audience
      }
    )
    
    // 生成刷新令牌
    const refreshToken = jwt.sign(
      {
        ...payload,
        type: TOKEN_TYPES.REFRESH,
        tokenId: generateUUID()
      },
      config.jwt.refreshSecret,
      {
        expiresIn: config.jwt.refreshExpiresIn,
        issuer: config.jwt.issuer,
        audience: config.jwt.audience
      }
    )
    
    return {
      accessToken,
      refreshToken,
      tokenType: 'Bearer',
      expiresIn: this.parseExpiresIn(config.jwt.expiresIn)
    }
  }
  
  /**
   * 解析过期时间字符串为秒数
   */
  static parseExpiresIn(expiresIn) {
    if (typeof expiresIn === 'number') {
      return expiresIn
    }
    
    const match = expiresIn.match(/(\d+)([smhd])/)
    if (!match) {
      return 3600 // 默认1小时
    }
    
    const value = parseInt(match[1])
    const unit = match[2]
    
    switch (unit) {
      case 's': return value
      case 'm': return value * 60
      case 'h': return value * 3600
      case 'd': return value * 86400
      default: return 3600
    }
  }
  
  /**
   * 获取用户权限
   */
  static getUserPermissions(user) {
    const basePermissions = [
      'chat:create',
      'chat:read',
      'chat:update',
      'chat:delete',
      'config:read',
      'config:create',
      'config:update',
      'config:delete',
      'profile:read',
      'profile:update'
    ]
    
    const adminPermissions = [
      ...basePermissions,
      'user:read',
      'user:create',
      'user:update',
      'user:delete',
      'system:read',
      'system:update',
      'admin:access'
    ]
    
    switch (user.role) {
      case USER_ROLES.ADMIN:
        return adminPermissions
      case USER_ROLES.STUDENT:
      default:
        return basePermissions
    }
  }
  
  /**
   * 检查用户权限
   */
  static hasPermission(user, permission) {
    const userPermissions = this.getUserPermissions(user)
    return userPermissions.includes(permission)
  }
}

module.exports = AuthService